----------------------------------------------------------------------------------
-- Company: 			NUS CEG Undergraduate
-- Engineer: 			PHANG TIAN HE
-- 
-- Create Date:    16:09:12 10/31/2012 
-- Design Name: 
-- Module Name:    Tmer_Handler - Timer_Control 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description: 
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.all;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity Timer_Handler is
    Port ( clk : in  STD_LOGIC;
           rst : in  STD_LOGIC;
           TMOD_reg : in  STD_LOGIC_VECTOR (7 downto 0);
           TCON_reg : in  STD_LOGIC_VECTOR (7 downto 0);
           TH0_reg : in  STD_LOGIC_VECTOR (7 downto 0);
           TL0_reg : in  STD_LOGIC_VECTOR (7 downto 0);
           TH1_reg : in  STD_LOGIC_VECTOR (7 downto 0);
           TL1_reg : in  STD_LOGIC_VECTOR (7 downto 0);
			  T1		 : in STD_LOGIC;
			  T0		 : in STD_LOGIC;
			  INT1	 : in STD_LOGIC;
			  INT0	 : in STD_LOGIC;
           Timer_Changed : out  STD_LOGIC;
           ack_changed : in  STD_LOGIC;
           OUT_TH0_reg : out  STD_LOGIC_VECTOR (7 downto 0);
           OUT_TL0_reg : out  STD_LOGIC_VECTOR (7 downto 0);
           OUT_TH1_reg : out  STD_LOGIC_VECTOR (7 downto 0);
           OUT_TL1_reg : out  STD_LOGIC_VECTOR (7 downto 0);
           OUT_TCON_reg : out  STD_LOGIC_VECTOR (7 downto 0));
end Timer_Handler;

architecture Timer_Control of Timer_Handler is

signal old_T1:std_logic;
signal old_T0:std_logic;
signal updated: std_logic;
signal Internal_counter			: std_logic_vector(3 downto 0);
signal old_ack	: std_logic;
begin
	process(rst, clk)
	variable Timer1_should_increase	: std_logic;
	variable Timer0_should_increase	: std_logic;
	variable Timer1_interrupt_flag	: std_logic;
	variable Timer0_interrupt_flag	: std_logic;
	variable Timer1_changed 			: std_logic; 
	variable Timer0_changed 			: std_logic; 
	begin
		if(rst = '1') then
			old_T1 <= T1;
			old_T0 <= T0;
			old_ack <= '0';
			updated <= '1';
			Timer1_should_increase := '0';
			Timer0_should_increase := '0';
			Timer1_interrupt_flag := '0'; 
			Timer0_interrupt_flag := '0';
			Timer1_changed := '0';
			Timer0_changed := '0';
			Internal_counter <= "0000";
			
			OUT_TL0_reg <= (others=> '0');
			OUT_TL1_reg <= (others=> '0');
			OUT_TH0_reg <= (others=> '0');
			OUT_TH1_reg <= (others=> '0');
			OUT_TCON_reg <= (others=> '0');
			Timer_Changed <= '0';
		
		elsif(clk'event and clk ='1') then
		
			-- ============================== Increase counter check ==============================
			-- ONLY CHECK WHETHER SHOULD INCREASE, ACTUALLY INCREMENT IS DETERMINED BY OTHER LOGIC
			if( Internal_counter = "1011" ) then
				Internal_counter <= "0000";
			else
				Internal_counter <= Internal_counter + 1;
			end if;
			
			-- ========================= Check Changed Acknowledgement flag =====================
			if( ack_changed = '1' ) then
				updated <= '1';
				Timer0_changed:= '0';
				Timer1_changed:= '0';
				Timer_Changed <= '0';
			end if;
			
			-- Timer 1 Checking
			if( TCON_reg(6) = '0') then
				-- If disable, comfirm not increase
				Timer1_should_increase := '0';
			elsif( TMOD_reg(6) = '0') then
				if( Internal_counter = "1011" ) then
					Timer1_should_increase := '1';
				else
					Timer1_should_increase := '0';
				end if;
			else
				if( old_T1 = '1' and T1 = '0' ) then
					Timer1_should_increase := '1';
				else
					Timer1_should_increase := '0';
				end if;
			end if;
			
			-- Timer 0 Checking
			if( TCON_reg(4) = '0') then
				-- If disable, comfirm not increase
				Timer0_should_increase := '0';
			elsif( TMOD_reg(3) = '0') then
				if( Internal_counter = "1011" ) then
					Timer0_should_increase := '1';
				else
					Timer0_should_increase := '0';
				end if;
			else
				if( old_T0 = '1' and T0 = '0' ) then
					Timer0_should_increase := '1';
				else
					Timer0_should_increase := '0';
				end if;
			end if;
			-- ====================================================================================
			
			-- ============================== Configure Output ====================================
			-- DETERMINE WHETHER WE SHOULD CHANGE THE OUTPUT
			if( updated = '1' ) then
				-- TR1 = '1' and (Gate_1 = '0' or INT1 = '1')
				if( TCON_reg(6) = '1' and (TMOD_reg(7) = '0' or INT1 = '1')) then
					case TMOD_reg(5 downto 4) is
						when "00"=>
							--============================== 13-bits C/T Mode =================================
							if (Timer1_should_increase = '1' ) then
								if( TH1_reg = "11111111" and TL1_reg(4 downto 0) = "11111") then
									Timer1_interrupt_flag := '1';
									OUT_TH1_reg <= x"00";
									OUT_TL1_reg <= x"00";
									Timer1_changed := '1';
								elsif( TH1_reg = x"FF" ) then
									Timer1_interrupt_flag := TCON_reg(7);
									OUT_TH1_reg <= x"00";
									OUT_TL1_reg <= TL1_reg + 1;
									Timer1_changed := '1';
								else
									Timer1_interrupt_flag := TCON_reg(7);
									OUT_TH1_reg <= TH1_reg + 1;
									OUT_TL1_reg <= TL1_reg;
									Timer1_changed := '1';
								end if;
							else
								Timer1_interrupt_flag := TCON_reg(7);
								OUT_TH1_reg <= TH1_reg;
								OUT_TL1_reg <= TL1_reg;
								Timer1_changed := '0';
							end if;
						when "01"=>
							--============================== 16-bits C/T Mode =================================
							if (Timer1_should_increase = '1' ) then
								if( TH1_reg = "11111111" and TL1_reg = "11111111") then
									Timer1_interrupt_flag := '1';
									OUT_TH1_reg <= x"00";
									OUT_TL1_reg <= x"00";
									Timer1_changed := '1';
								elsif( TL1_reg = x"FF" ) then
									Timer1_interrupt_flag := TCON_reg(7);
									OUT_TL1_reg <= x"00";
									OUT_TH1_reg <= TH1_reg + 1;
									Timer1_changed := '1';
								else
									Timer1_interrupt_flag := TCON_reg(7);
									OUT_TH1_reg <= TH1_reg;
									OUT_TL1_reg <= TL1_reg + 1;
									Timer1_changed := '1';
								end if;
							else
								Timer1_interrupt_flag := TCON_reg(7);
								OUT_TH1_reg <= TH1_reg;
								OUT_TL1_reg <= TL1_reg;
								Timer1_changed := '0';
							end if;
						when "10"=>
							--============================== Auto-Reload Mode =================================
							if (Timer1_should_increase = '1' ) then
								if( TL1_reg = x"FF") then
									Timer1_interrupt_flag := '1';
									OUT_TH1_reg <= TH1_reg;
									OUT_TL1_reg <= TH1_reg;
									Timer1_changed := '1';
								else
									Timer1_interrupt_flag := TCON_reg(7);
									OUT_TH1_reg <= TH1_reg;
									OUT_TL1_reg <= TL1_reg + 1;
									Timer1_changed := '1';
								end if;
							else
								Timer1_interrupt_flag := TCON_reg(7);
								OUT_TH1_reg <= TH1_reg;
								OUT_TL1_reg <= TL1_reg;
								Timer1_changed := '0';
							end if;
						when "11"=>
							--============================== (Mode 3) 3 Timers Mode  =================================
							-- If Timer1 set to Mode 3, Just hold its count, like disable
							Timer1_interrupt_flag := TCON_reg(7);
							OUT_TH1_reg <= TH1_reg;
							OUT_TL1_reg <= TL1_reg;
							Timer1_changed := '0';
						when others=>
					end case;
				end if;
				
				-- TR0 = '1' and (Gate_0 = '0' or INT0 = '1')	
				if( TCON_reg(4) = '1' and (TMOD_reg(3) = '0' or INT0 = '1')) then
					case TMOD_reg(1 downto 0) is
						when "00"=>
							--============================== 13-bits C/T Mode =================================
							if (Timer0_should_increase = '1' ) then
								if( TH0_reg = "11111111" and TL0_reg(4 downto 0) = "11111") then
									Timer0_interrupt_flag := '1';
									OUT_TH0_reg <= x"00";
									OUT_TL0_reg <= x"00";
									Timer0_changed := '1';
								elsif( TH0_reg = x"FF" ) then
									Timer0_interrupt_flag := TCON_reg(5);
									OUT_TH0_reg <= x"00";
									OUT_TL0_reg <= TL0_reg + 1;
									Timer0_changed := '1';
								else
									Timer0_interrupt_flag := TCON_reg(5);
									OUT_TH0_reg <= TH0_reg + 1;
									OUT_TL0_reg <= TL0_reg;
									Timer0_changed := '1';
								end if;
							else
								Timer0_interrupt_flag := TCON_reg(5);
								OUT_TH0_reg <= TH0_reg;
								OUT_TL0_reg <= TL0_reg;
								Timer0_changed := '0';
							end if;
						when "01"=>
							--============================== 16-bits C/T Mode =================================
							if (Timer0_should_increase = '1' ) then
								if( TH0_reg = x"FF" and TL0_reg = x"FF") then
									Timer0_interrupt_flag := '1';
									OUT_TH0_reg <= x"00";
									OUT_TL0_reg <= x"00";
									Timer0_changed := '1';
								elsif( TL0_reg = x"FF" ) then
									Timer0_interrupt_flag := TCON_reg(5);
									OUT_TL0_reg <= x"00";
									OUT_TH0_reg <= TH0_reg + 1;
									Timer0_changed := '1';
								else
									Timer0_interrupt_flag := TCON_reg(5);
									OUT_TH0_reg <= TH0_reg;
									OUT_TL0_reg <= TL0_reg + 1;
									Timer0_changed := '1';
								end if;
							else
								Timer0_interrupt_flag := TCON_reg(5);
								OUT_TH0_reg <= TH0_reg;
								OUT_TL0_reg <= TL0_reg;
								Timer0_changed := '0';
							end if;
						when "10"=>
							--============================== Auto-Reload Mode =================================
							if (Timer0_should_increase = '1' ) then
								if( TL0_reg = x"FF") then
									Timer0_interrupt_flag := '1';
									OUT_TH0_reg <= TH0_reg;
									OUT_TL0_reg <= TH0_reg;
									Timer0_changed := '1';
								else
									Timer0_interrupt_flag := TCON_reg(5);
									OUT_TH0_reg <= TH0_reg;
									OUT_TL0_reg <= TL0_reg + 1;
									Timer0_changed := '1';
								end if;
							else
								Timer0_interrupt_flag := TCON_reg(5);
								OUT_TH0_reg <= TH0_reg;
								OUT_TL0_reg <= TL0_reg;
								Timer0_changed := '0';
							end if;
						when "11"=>
							--============================== (Mode 3) 3 Timers Mode  =================================
							-- TL0 respond to Timer0 Setting in TMOD & TCON
							-- TH0 respond to Timer1 Setting in TMOD & TCON
							-- 2x8 bits Timers
							
							--=========== TL0 Session ===============
							if (Timer0_should_increase = '1' ) then
								if( TL0_reg = x"FF") then
									Timer0_interrupt_flag := '1';
									OUT_TL0_reg <= x"00";
									Timer0_changed := '1';
								else
									Timer0_interrupt_flag := TCON_reg(5);
									OUT_TL0_reg <= TL0_reg + 1;
									Timer0_changed := '1';
								end if;
							else
								Timer0_interrupt_flag := TCON_reg(5);
								OUT_TL0_reg <= TL0_reg;
								Timer0_changed := '0';
							end if;
							
							--=========== TH0 Session ===============
							if (Timer1_should_increase = '1' ) then
								if( TH0_reg = x"FF") then
									Timer1_interrupt_flag := '1';
									OUT_TH0_reg <= x"00";
									Timer1_changed := '1';
								else
									Timer1_interrupt_flag := (TCON_reg(7) or Timer1_interrupt_flag);
									OUT_TH0_reg <= TH0_reg + 1;
									Timer1_changed := '1';
								end if;
							else
								Timer1_interrupt_flag := (TCON_reg(7) or Timer1_interrupt_flag);
								OUT_TH0_reg <= TH0_reg;
								Timer1_changed := '0';
							end if;
						when others=>
					end case;
				end if;
				
				
				-- =================== Output the change TCON_reg =======================
				if( Timer1_changed = '1' or Timer0_changed = '1') then
					Timer_Changed <= '1';
					updated <= '0';
					OUT_TCON_reg <= Timer1_interrupt_flag & TCON_reg(6) & Timer0_interrupt_flag & TCON_reg(4 downto 0);
				else 
					OUT_TCON_reg <= TCON_reg;
				end if;
				
				
			else
				-- If the previous changed haven't been acknowlage, don't change the output at all
			end if;
			-- ====================================================================================
			old_ack <= ack_changed;
		end if;
		
		old_T1 <= T1;
		old_T0 <= T0;
	end process;

end Timer_Control;

